/* Copyright 2009 Sony Mathew, Minneapolis MN. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at  http://www.apache.org/licenses/LICENSE-2.0  Unless required by applicable law or agreed to in writing, software  distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */
package com.xsm.lite.util;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;

/**
 * Provides simple utilities for a String.
 * 
 * @author Sony Mathew
 */
public class StringUtil {

    /**
     * Tokenizes a String by '/' unique to path (e.g. URL paths).
     * 
     * E.g. 
     * 
     * "/aaa/bbb/ccc/" returns String[] {"aaa","bbb","ccc"}.
     *  " /aaa/bbb/ccc/ " returns String[] {" ", "aaa","bbb","ccc", " "}.
     * 
     * "/" returns String[0]
     *  "" returns String[0]
     * 
     * null returns String[0]
     * 
     * author Sony Mathew
     */
    public static String[] pathSplit(String s) {
        String delim = "/";
        
        if (isEmpty(s)) {
            return new String[0];
        }

        // Note: changed to use this instead of original StringTokenizer
        // implementation. The StringTokenizer did not correctly handle
        // cases of 2 adjacent delimiters--it skipped over it entirely
        // rather than considering it an empty token.

        // remove leading slash, if any
        if (s.startsWith(delim)) {
            s = s.substring(1);
            if (isEmpty(s)) {
                return new String[0];
            }
        }

        return s.split(delim);

    }
    
    /**
     * Same as String.split(delim) except returns String[0] for null.
     * Provide the regular expression that acts as the delimitor.
     * 
     * author Sony Mathew
     */
    public static String[] split(String s, String delim) {
        if (s == null) {
            return new String[0];
        }

        return s.split(delim);
    }
    
    /**
     * Does a split using any whitespace character.
     * 
     * @see #cleanSplit(String, String)
     * 
     * author Sony Mathew
     */
    public static String[] split(String s) {
        return split(s, "\\s");
    }
    
    /**
     * Strictly splits by token and does not ignore tokens if they are empty.
     * if a delim exists then everthing before it to the previous delim is a token even if empty.
     * Preserves whitespace on tokens between delim.
     * 
     * @param delim: one character only.
     * 
     * author Sony Mathew
     */
    public static String[] strictSplit(String s, char delim) {
        if (s == null) {
            return new String[0];
        }

        ArrayList<String> splitResult = new ArrayList<String>();
        strictSplit(s, delim, splitResult);
        
        return splitResult.toArray(new String[splitResult.size()]);
    }

    /**
     * Collect the split result recursively;
     * 
     * author Sony Mathew
     */
    private static void strictSplit(String s, char delim, ArrayList<String> splitResult) {
        if (s == null) {
            return;
        }

        int idelim = s.indexOf(delim);
        
        if (idelim < 0) {
            splitResult.add(s);
            return;
        }
        
        String token = s.substring(0, idelim);
        splitResult.add(token);
        
        if (idelim+1 < s.length()) {
            s = s.substring(idelim+1);
            strictSplit(s, delim, splitResult);
        }        
    }
    
    /**
     * Same as split(str,delim) except removes any tokens that are empty when trimmed.
     * 
     * Will return String[0] if no clean tokens exist or null was passed in.
     * 
     * Provide the regular expression that acts as the delimitor.
     *  e.g. \s = all whitespace, [ab] = a or b.
     * 
     * author Sony Mathew
     */
    public static String[] cleanSplit(String s, String delim) {
        if (s == null) {
            return new String[0];
        }
        
        String[] origSplit = split(s, delim);
        if (origSplit.length == 0) {
            return origSplit;
        }
        
        List<String> cleanSplit = new ArrayList<String>(origSplit.length);
        for(int i=0;i<origSplit.length;i++) {
            String cs = clean(origSplit[i]);
            if (cs.length()>0) {
                cleanSplit.add(cs);
            }
        }
        
        return cleanSplit.toArray(new String[cleanSplit.size()]);
    }
    
    /**
     * Does a clean split using any whitespace character.
     * 
     * @see #cleanSplit(String, String)
     * 
     * author Sony Mathew
     */
    public static String[] cleanSplit(String s) {
        return cleanSplit(s, "\\s");
    }
    
    /**
     * @see #concat(List, String)
     * 
     * author Sony Mathew
     */
    public static String concat(String[] tokens, String delim) {
        if (tokens == null || tokens.length == 0) {
            return "";
        }

        return concat(Arrays.asList(tokens), delim);
    }

    /**
     * Concatentates the String tokens using the given delim.
     * 
     * @return String : Concatented string or Empty string if no tokens.
     * 
     * author Sony Mathew
     */
    public static String concat(List tokens, String delim) {
        if (tokens == null || tokens.size() == 0) {
            return "";
        }

        String concat = null;

        for (Iterator tokenIter = tokens.iterator(); tokenIter.hasNext();) {
            Object token = tokenIter.next();
            if (token != null) {
                concat = concat==null? "" : concat+delim;
                concat += token.toString();
            }
        }

        return concat;
    }

    /**
     * Properly escapes characters such as spaces in a URL string.
     * 
     * @param url
     * @return escapedURL : String
     * @author vkakula
     */
    public static String escapeURL(String url) {

        final String UNCHANGED_SPECIAL_CHARS = ":+-*_/.@?()[]&=~";

        if (url == null || "".equals(url.trim())) {
            return url;
        }

        StringBuilder escapedUrlBuf = new StringBuilder();
        char[] charArr = url.toCharArray();

        for (Character nextChar : charArr) {
            if (Character.isLetterOrDigit(nextChar)
                    || (UNCHANGED_SPECIAL_CHARS.indexOf(nextChar) > -1)) {
                escapedUrlBuf.append(nextChar);
                continue;
            }

            // Convert the character to be escaped to Hexadecimal ASCII while
            // prepending with %
            escapedUrlBuf.append("%").append(
                    Integer.toHexString((int) nextChar.charValue()));
        }

        return escapedUrlBuf.toString();
    }

    /**
     * Returns true if the string consists of whitespace characters.
     * 
     * @param s
     *            the input string
     * 
     * @return true if the string consists only of whitespace characters.
     * 
     * @see java.lang.Character#isWhitespace(char)
     */
    public static boolean isWhitespace(String s) {
        int ls = s.length();

        for (int i = 0; i < ls; i++) {
            if (Character.isLetterOrDigit(s.charAt(i))) {
                return false;
            }
        }

        return true;
    }

    /**
     * Will trim the string, or return an empty string if null.
     * 
     * author Sony Mathew
     */
    public static String clean(String s) {
        return (s != null)? s.trim() : "";
    }

    
    /**
     * author Sony Mathew
     */
    public static boolean isEmpty(String s) {
        return (clean(s).length() == 0);
    }
    
    public static boolean isNotEmpty(String s) {
    	return ! isEmpty(s);
    }

    /**
     * Compares two Strings, returning true if they are equal.
     * Nulls are handled without exceptions. Two <code>null</code>
     * references are considered equal. Comparison is case sensitive.
     *
     * @see java.lang.String#equals(Object)
     * @param str1  the first string
     * @param str2  the second string
     * @return true if the Strings are equal, case sensitive, or both null
     */
    public static boolean equals(String str1, String str2) {
        return (str1 == null ? str2 == null : str1.equals(str2));
    }

    /**
     * Compares two Strings, returning true if they are equal ignoring case.
     * Nulls are handled without exceptions. Two <code>null</code>
     * references are considered equal. Comparison is case insensitive.
     * @see java.lang.String#equalsIgnoreCase(String)
     * @param str1  the first string
     * @param str2  the second string
     * @return true if the Strings are equal, case insensitive, or both null
     */
    public static boolean equalsIgnoreCase(String str1, String str2) {
        return (str1 == null ? str2 == null : str1.equalsIgnoreCase(str2));
    }

    /**
     * Gets the leftmost n characters of a string. If n characters are not 
     * available, or the string is null, the string will be returned 
     * without an exception.
     *
     * @param str  the string to get the leftmost characters from
     * @param len  the length of the required string
     * @return the leftmost characters
     * @throws IllegalArgumentException if len is less than zero
     */
    public static String left(String str, int len) {
        if (len < 0) {
            throw new IllegalArgumentException("Requested String length " + len + " is less than zero");
        }
        if ((str == null) || (str.length() <= len)) {
            return str;
        } else {
            return str.substring(0, len);
        }
    }
    
    /**
     * Left pad a String with a specified string. Pad to a size of n.
     *
     * @param str  String to pad out
     * @param size  int size to pad to
     * @param delim  String to pad with
     * @return left padded String
     * @throws NullPointerException if str or delim is null
     * @throws ArithmeticException if delim is the empty string
     */
    public static String leftPad(String str, int size, String delim) {
        size = (size - str.length()) / delim.length();
        if (size > 0) {
            str = repeat(delim, size) + str;
        }
        return str;
    }

    /**
     * Gets the rightmost n characters of a string. If n characters are not
     * available, or the string is null, the string will be returned without an
     * exception.
     * 
     * @param str
     *            the string to get the rightmost characters from
     * @param len
     *            the length of the required string
     * @return the leftmost characters
     * @throws IllegalArgumentException
     *             if len is less than zero
     */
    public static String right(String str, int len) {
        if (len < 0) {
            throw new IllegalArgumentException("Requested String length " + len
                    + " is less than zero");
        }
        if ((str == null) || (str.length() <= len)) {
            return str;
        } else {
            return str.substring(str.length() - len);
        }
    }

    /**
     * Checks if the string contains only unicode digits. Null will return
     * false. The empty string will return true.
     * 
     * @param str
     *            the string to check
     * @return true if only contains digits, and is non-null
     */
    public static boolean isNumeric(String str) {
        if (str == null) {
            return false;
        }
        int sz = str.length();
        for (int i = 0; i < sz; i++) {
            if (Character.isDigit(str.charAt(i)) == false) {
                return false;
            }
        }
        return true;
    }

    /**
     * Alternative to StringBuffer.reverse(String) which is not supported by GWT.
     * 
     * author Sony Mathew
     */
    public static String reverse(String str) {
        if (str == null || str.length()==0) {
            return str;
        }
        StringBuilder revStr = new StringBuilder();
        for(int i=str.length()-1;i>=0;i--) {
            revStr.append(str.charAt(i));
        }
        return revStr.toString();
    }

    /**
     * Repeat a string n times to form a new string.
     *
     * @param str  String to repeat
     * @param repeat  int number of times to repeat
     * @return String with repeated string
     * @throws NegativeArraySizeException if repeat < 0
     * @throws NullPointerException if str is null
     */
    public static String repeat(String str, int repeat) {
        StringBuffer buffer = new StringBuffer(repeat * str.length());
        for (int i = 0; i < repeat; i++) {
            buffer.append(str);
        }
        return buffer.toString();
    }

    /**
     * Right pad a String with spaces. Pad to a size of n.
     * 
     * @param str  String to repeat
     * @param size  int number of times to repeat
     * @return right padded String
     * @throws NullPointerException if str is null
     */
    public static String rightPad(String str, int size) {
        return rightPad(str, size, " ");
    }
    
    /**
     * Right pad a String with a specified string. Pad to a size of n.
     *
     * @param str  String to pad out
     * @param size  int size to pad to
     * @param delim  String to pad with
     * @return right padded String
     * @throws NullPointerException if str or delim is null
     * @throws ArithmeticException if delim is the empty string
     */
    public static String rightPad(String str, int size, String delim) {
        size = (size - str.length()) / delim.length();
        if (size > 0) {
            str += repeat(delim, size);
        }
        return str;
    }
    
    /**
     * Returns a new String consisting of the input string prefixed and
     * suffixed with a double quote character. If the input is null,
     * returns a String consisting of 2 double quote characters.
     * 
     * @param str
     * @return
     */
    public static String quote(String str) {
    	
    	return str == null 
    		? "\"\""
    		: "\"" + str + "\"";
    	
    }
    
    /**
     * A simple utility to obtain a readable string an object.
     * It protects against null objects and objects that may accidently throw an exception in their toString().
     * 
     * author Sony Mathew
     */
    public static String toString(Object o) {
        if (o == null) {
            return "null";
        }
        
        try {
            return o.toString();
        }catch(Throwable t) {
            return o.getClass().getName() + "/toString()=" + t.getMessage();
        }
    }    

    /**
     * Strip any of a supplied string from the start of a String.
     * 
     * @param str  the string to remove characters from
     * @param strip  the string to remove
     * @return the stripped string
     */
    public static String stripStart(String str, String strip) {
        if (str == null) {
            return null;
        }
        if (strip == null) {
        	return str;
        }
 
        int start = 0;
 
        int sz = str.length();
 
        while ((start != sz) && (strip.indexOf(str.charAt(start)) != -1)) {
            start++;
        }
        return str.substring(start);
    }

    /**
     * A simple utility to obtain a readable string an object for debugging purposes.
     * It protects against null objects and objects that may accidently throw an exception in their toString().
     * 
     * author Sony Mathew
     */
    public static String toDebugString(Object o) {
        if (o == null) {
            return "null";
        }

        if (o instanceof Debuggable) {        
            try {
                    return ((Debuggable)o).toDebugString();
            }catch(Throwable t) {
                return o.getClass().getName() + "/toDebugString()=" + t.getMessage();
            }
        }else{
            return toString(o);
        }            
    }    
    
    /**
     * 
     * A utility to trim the front of a string with any character
     * ex.  "12345" would be returned from trimFront("00000012345",'0');
     */
    public static String trimFront(String text, char character) {
        int index;

        if (text == null) {
          return text;
        }

        index = 0;

        while (text.charAt(index) == character) {
          index++;
        }
        return text.substring(index).trim();
      }

}
